package org.hawk.gwt.ppc.introspection.impl;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.hawk.gwt.ppc.introspection.IntrospectionException;
import org.hawk.gwt.ppc.introspection.JavaConstructor;
import org.hawk.gwt.ppc.introspection.JavaField;
import org.hawk.gwt.ppc.introspection.JavaMethod;
import org.hawk.gwt.ppc.introspection.JavaTypeScope;
import org.hawk.gwt.ppc.introspection.JavaTypeVariable;
import org.hawk.gwt.ppc.parser.BlockDescriptor;
import org.hawk.gwt.ppc.parser.ClassDescriptor;
import org.hawk.gwt.ppc.parser.ClassDescriptor.ClassDescriptorType;
import org.hawk.gwt.ppc.parser.ConstructorDescriptor;
import org.hawk.gwt.ppc.parser.Descriptor;
import org.hawk.gwt.ppc.parser.FieldDescriptor;
import org.hawk.gwt.ppc.parser.MethodDescriptor;
import org.hawk.gwt.ppc.utils.Name;

/**
 * Type scope for types with source code available.
 *  
 * @author alex.bereznevatiy@gmail.com
 *
 */
final class SourceJavaTypeScope extends AbstractJavaTypeScope {
	
	private ClassDescriptor descriptor;
	private List<JavaConstructor> constructors;
	private List<JavaMethod> methods;
	private Map<String, JavaField> fields;
	private List<JavaTypeScope> declaredClasses;

	SourceJavaTypeScope(ClassDescriptor descriptor, JavaScopeImpl parent) {
		super(Name.getRawName(descriptor.getName()), getExtendsSection(descriptor), getImplementsSection(descriptor));
		this.descriptor = descriptor;
		init(descriptor, parent);
	}

	private static List<String> getImplementsSection(ClassDescriptor descriptor) {
		if (descriptor.getType().equals(ClassDescriptorType.INTERFACE) ||
				descriptor.getName().length() == 0) {
			return descriptor.getExtendsSection();
		}
		if (descriptor.getType().equals(ClassDescriptorType.ANNOTATION)) {
			return Collections.singletonList(Annotation.class.getName());
		}
		return descriptor.getImplementsSection();
	}

	private static String getExtendsSection(ClassDescriptor descriptor) {
		if (descriptor.getType().equals(ClassDescriptorType.INTERFACE) ||
				descriptor.getType().equals(ClassDescriptorType.ANNOTATION)) {
			return null;
		}
		if (descriptor.getType().equals(ClassDescriptorType.ENUM)) {
			return Enum.class.getName();
		}
		if (descriptor.getName().length() == 0 || 
				descriptor.getExtendsSection().isEmpty()) {
			return Object.class.getName();
		}
		if (descriptor.getExtendsSection().size() != 1) {
			throw new IntrospectionException("Extends section should contain only single ");
		}
		return descriptor.getExtendsSection().get(0);
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#isInterface()
	 */
	public boolean isInterface() {
		return descriptor.getType().equals(ClassDescriptorType.INTERFACE);
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#isClass()
	 */
	public boolean isClass() {
		return descriptor.getType().equals(ClassDescriptorType.CLASS);
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#isEnum()
	 */
	public boolean isEnum() {
		return descriptor.getType().equals(ClassDescriptorType.ENUM);
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#isAnnotation()
	 */
	public boolean isAnnotation() {
		return descriptor.getType().equals(ClassDescriptorType.ANNOTATION);
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getConstructors()
	 */
	public List<JavaConstructor> getConstructors() {
		if (constructors != null) {
			return constructors;
		}
		ArrayList<JavaConstructor> result = new ArrayList<JavaConstructor>();
		for (ConstructorDescriptor constructor : descriptor.getConstructors()) {
			result.add(new JavaConstcructorImpl(constructor, this));
		}
		if (result.isEmpty() && isClass()) {
			result.add(JavaConstcructorImpl.createDefault(this));
		}
		result.trimToSize();
		constructors = Collections.unmodifiableList(result);
		
		return constructors;
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getMethods()
	 */
	public List<JavaMethod> getMethods() {
		if (methods != null) {
			return methods;
		}
		ArrayList<JavaMethod> result = new ArrayList<JavaMethod>();
		for (MethodDescriptor method : descriptor.getMethods()) {
			result.add(new JavaMethodImpl(method, this));
		}
		result.trimToSize();
		methods = Collections.unmodifiableList(result);
		
		return methods;
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getFields()
	 */
	public List<JavaField> getFields() {
		if (fields == null) {
			resolveFields();
		}
		return Collections.unmodifiableList(new ArrayList<JavaField>(fields.values()));
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.impl.AbstractJavaTypeScope#getDeclaredField(java.lang.String)
	 */
	JavaField getDeclaredField(String name) {
		if (fields == null) {
			resolveFields();
		}
		return fields.get(name);
	}

	private void resolveFields() {
		fields = new LinkedHashMap<String, JavaField>();
		for (FieldDescriptor field : descriptor.getFields()) {
			fields.put(field.getName(), new JavaFieldImpl(field, this));
		}
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getDeclaredClasses()
	 */
	public List<JavaTypeScope> getDeclaredClasses() {
		if (declaredClasses != null) {
			return declaredClasses;
		}
		ArrayList<JavaTypeScope> result = new ArrayList<JavaTypeScope>();
		for (ClassDescriptor clazz : descriptor.getDeclaredClasses()) {
			result.add(new SourceJavaTypeScope(clazz, this));
		}
		result.trimToSize();
		declaredClasses = Collections.unmodifiableList(result);
		return declaredClasses;
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#isPrimitive()
	 */
	public boolean isPrimitive() {
		return false;
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#isPrimitiveWrapper()
	 */
	public boolean isPrimitiveWrapper() {
		return false;
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaParameterizable#getTypeVariables()
	 */
	public List<JavaTypeVariable> getTypeVariables() {
		String params = descriptor.getTypeParameters();
		return GenericHelper.buildTypeVariables(params, this);
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.impl.JavaScopeImpl#parseChildren(org.hawk.gwt.ppc.parser.Descriptor)
	 */
	@Override
	void parseChildren(Descriptor descriptor) {
		ClassDescriptor clazz = (ClassDescriptor)descriptor;
		for (BlockDescriptor block : clazz.getInitializers()) {
			JavaScopeImpl scope = new JavaScopeImpl();
			scope.init(block, this);
			addChild(scope);
		}
	}
}
